﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
using Core;

public class ABRedundancyChecker
{
    private class CRedAsset
    {
        public string mName = "";
        public string mType = "";
        public List<string> mUsers = new List<string>();
    }

    private const string Dir = "a_ABRedundency"; //输出文件的目录
    private string kResultPath = "";
    private string mABPath = "";
    private string mMainAb = "";

    private List<string> mAllABFiles = null;
    private Dictionary<string, string> mAssetGenMap = null;
    private Dictionary<string, CRedAsset> mRedAssetMap = null;
    private float mCheckTime = 0f;

    private Action<string> OnFinish;

    // 提供给其他脚本调用的接口
    public void StartCheck(string path, string platform, string abName, Action<string> func)
    {
        mABPath = Path.Combine(path, platform);
        kResultPath = Path.Combine(path, Dir);
        mMainAb = abName;
        OnFinish = func;
        StartCheck();
    }

    private void StartCheck()
    {
        EditorUtility.DisplayCancelableProgressBar("AB资源冗余检测中", "资源读取中......", 0f);

        mCheckTime = Time.realtimeSinceStartup;
        if (mAllABFiles == null)
            mAllABFiles = new List<string>();
        if (mAssetGenMap == null)
            mAssetGenMap = new Dictionary<string, string>();
        if (mRedAssetMap == null)
            mRedAssetMap = new Dictionary<string, CRedAsset>();

        if (!GenAssetMap(mABPath, mMainAb))
        {
            EditorUtility.ClearProgressBar();
            EditorUtility.DisplayDialog("错误", "请检查是否选择正确的AB资源", "Ok");
            return;
        }

        GetAllFiles(mAllABFiles, mABPath);
        startIndex = 0;

        EditorApplication.update += CustomUpdate;
    }
    private int startIndex;
    private void CustomUpdate()
    {
        if (mAllABFiles.Count == 0)
        {
            EditorUtility.ClearProgressBar();
            EditorApplication.update -= CustomUpdate;
            return;
        }

        string file = mAllABFiles[startIndex];
        AssetBundle ab = null;
        string ab_name = string.Empty;
        try
        {
            ab = CreateABAdapter(file);
            ab_name = ab.name;
            //string[] arr = file.Split('/');
            //CheckABInfo(ab, arr[arr.Length - 1]);
            CheckABInfo(ab, file);
        }
        catch (Exception e)
        {
            Debug.LogError("MyError:" + e.StackTrace);
        }
        finally
        {
            if (ab != null)
                ab.Unload(true);
        }
        Debug.Log($"AB资源冗余检测中 {startIndex}/{mAllABFiles.Count} {ab_name}");

        bool isCancel = EditorUtility.DisplayCancelableProgressBar("AB资源冗余检测中", $"{startIndex}/{mAllABFiles.Count} {ab_name}", startIndex / (float)mAllABFiles.Count);
        startIndex++;
        if (startIndex >= mAllABFiles.Count)
        {
            EditorUtility.ClearProgressBar();
            if (!isCancel)
            {
                CullNotRed();
                mCheckTime = Time.realtimeSinceStartup - mCheckTime;
                EditorUtility.ClearProgressBar();
                EditorUtility.DisplayDialog("AssetBundle资源冗余检测结果", Export(), "Ok");
            }

            ClearAssets();
            EditorApplication.update -= CustomUpdate;
            startIndex = 0;
        }
    }

    private void ClearAssets()
    {
        mAllABFiles.Clear();
        mAllABFiles = null;
        mAssetGenMap.Clear();
        mAssetGenMap = null;
        mRedAssetMap.Clear();
        mRedAssetMap = null;
        Resources.UnloadUnusedAssets();
        EditorUtility.UnloadUnusedAssetsImmediate();
        GC.Collect();
    }

    //适配项目打包（有加密） 或 原生打包
    private AssetBundle CreateABAdapter(string path)
    {
        return AssetBundle.LoadFromFile(path);
    }

    private bool GenAssetMap(string path, string maniFest)
    {
        path = path.Replace("\\", "/");
        AssetBundle maniFestAb = CreateABAdapter(Path.Combine(path, maniFest));
        if (maniFestAb == null)
            return false;

        AssetBundleManifest manifest = maniFestAb.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
        if (manifest == null)
            return false;

        string[] allBundles = manifest.GetAllAssetBundles();
        maniFestAb.Unload(true);
        foreach (string abName in allBundles)
        {
            string filePath = Path.Combine(path, abName);
            AssetBundle ab = CreateABAdapter(filePath);
            try
            {
                foreach (string asset in ab.GetAllAssetNames())
                {
                    mAssetGenMap.Add(asset.ToLower(), abName);
                }
                foreach (string asset in ab.GetAllScenePaths())
                {
                    mAssetGenMap.Add(asset.ToLower(), abName);
                }
            }
            catch (Exception)
            {

            }
            finally
            {
                ab.Unload(true);
            }
        }

        if (mAssetGenMap.Count == 0)
            return false;

        return true;
    }

    private void CheckABInfo(AssetBundle ab, string ab_path)
    {
        EditorSettings.serializationMode = SerializationMode.ForceText;
        string[] names = ab.GetAllAssetNames();
        string[] dependencies = AssetDatabase.GetDependencies(names);
        string[] allDepen = dependencies.Length > 0 ? dependencies : names;

        for (int i = 0; i < allDepen.Length; i++)
        {
            string currDep = allDepen[i].ToLower();
            CalcuDenpend(currDep, ab_path);
        }
    }

    //todo: 待加入 类型
    private void CalcuDenpend(string depName, string ab_path)
    {
        if (depName.EndsWith(".cs"))
            return;

        if (!mAssetGenMap.ContainsKey(depName)) //不存在这个ab，记录一下
        {
            if (!mRedAssetMap.ContainsKey(depName))
            {
                CRedAsset ra = new CRedAsset();
                ra.mName = depName;
                ra.mType = "";
                mRedAssetMap.Add(depName, ra);
                ra.mUsers.Add(ab_path);
            }
            else
            {
                CRedAsset ra = mRedAssetMap[depName];
                ra.mUsers.Add(ab_path);
            }
        }
    }

    // mRedAssetMap 中 CRedAsset 的 mUsers 只有一个的，视为不冗余的资源，直接打到了该 ab 中
    private void CullNotRed()
    {
        List<string> keys = new List<string>();
        foreach (var item in mRedAssetMap)
        {
            if (item.Value.mUsers.Count == 1)
                keys.Add(item.Key);
        }

        foreach (var value in keys)
            mRedAssetMap.Remove(value);
    }

    private List<string> GetAllFiles(List<string> files, string folder)
    {
        folder = folder.Replace("\\", "/");
        DirectoryInfo dir = new DirectoryInfo(folder);
        foreach (var file in dir.GetFiles("*.*"))
        {
            if (file.Extension == ".manifest")
            {
                continue;
            }
            if (Utils.File.GetRegularPath(file.FullName.ToLower()) == Utils.File.GetRegularPath(mABPath.ToLower()))
            {
                continue;
            }
            files.Add((Path.Combine(folder, file.Name).Replace("\\", "/")).ToLower());
        }
        foreach (var sub in dir.GetDirectories())
        {
            files = GetAllFiles(files, Path.Combine(folder, sub.Name));
        }
        return files;
    }

    private string Export()
    {
        if (mRedAssetMap.Count == 0)
            return "未检查到有资源冗余";

        List<CRedAsset> raList = mRedAssetMap.Values.ToList();
        string currTime = DateTime.Now.ToString("yyyyMMdd_HHmmss");
        string path = string.Format("{0}/{1}_{2}.csv", kResultPath, "ABRedundency", currTime);
        if (!Directory.Exists(kResultPath))
            Directory.CreateDirectory(kResultPath);

        var m_sw = new StreamWriter(path);


        m_sw.WriteLine(string.Format("// 资源总量:{0}，冗余总量:{1}，检测时间:{2}，耗时：{3:F2}s", mAllABFiles.Count, raList.Count, currTime, mCheckTime));
        m_sw.WriteLine("---");
        m_sw.WriteLine("序号,资源名称,资源类型,AB文件数量,AB文件名");

        CRedAsset ra = null;

        raList.Sort((CRedAsset ra1, CRedAsset ra2) =>
        {
            int ret = ra2.mUsers.Count.CompareTo(ra1.mUsers.Count);
            if (ret == 0)
                ret = ra1.mName.CompareTo(ra2.mName);
            return ret;
        });

        for (int i = 0; i < raList.Count; i++)
        {
            ra = raList[i];

            if (ra.mUsers.Count <= 1)
            {
                continue;
            }

            m_sw.WriteLine($"{i},{ra.mName},{ra.mType},{ra.mUsers.Count},{string.Join("|", ra.mUsers)}");
        }

        m_sw.Flush();
        m_sw.Close();

        OnFinish?.Invoke(path);

        return "有冗余，导出结果：" + path.Replace("\\", "/");
    }
}